﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Errors;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Rights;
using EffiProz.Core.Store;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class ParserCommand : ParserDDL
    {

        public ParserCommand(Session session, Scanner t)
            : base(session, t)
        {

        }

        public Statement compileStatement()
        {

            Statement cs = compilePart();

            if (token.tokenType == Tokens.X_ENDPARSE)
            {
                if (cs.getSchemalName() == null)
                {
                    cs.setSchemaQName(session.getCurrentSchemaHsqlName());
                }

                return cs;
            }

            throw unexpectedToken();
        }

       public EfzArrayList compileStatements(String sql, int returnType)
        {

            EfzArrayList list = new EfzArrayList();
            Statement cs = null;

            reset(sql);

            while (true)
            {
                if (token.tokenType == Tokens.X_ENDPARSE)
                {
                    break;
                }

                cs = compilePart();

                list.add(cs);
            }

            if (returnType != StatementTypes.RETURN_ANY)
            {
                int group = cs.getGroup();

                if (group == StatementTypes.X_SQL_DATA)
                {
                    if (returnType == StatementTypes.RETURN_COUNT)
                    {
                        throw Error.error(ErrorCode.X_07503);
                    }
                }
                else if (returnType == StatementTypes.RETURN_RESULT)
                {
                    throw Error.error(ErrorCode.X_07504);
                }
            }

            return list;
        }

        private Statement compilePart()
        {

            Statement cs;

            compileContext.reset();
            setParsePosition(getPosition());

            if (token.tokenType == Tokens.X_STARTPARSE)
            {
                read();
            }

            switch (token.tokenType)
            {

                // DQL
                case Tokens.WITH:
                case Tokens.OPENBRACKET:
                case Tokens.VALUES:
                case Tokens.TABLE:
                case Tokens.SELECT:
                    {
                        cs = compileCursorSpecification();

                        break;
                    }

                // DML
                case Tokens.INSERT:
                    {
                        cs = compileInsertStatement(RangeVariable.emptyArray);

                        break;
                    }
                case Tokens.UPDATE:
                    {
                        cs = compileUpdateStatement(RangeVariable.emptyArray);

                        break;
                    }
                case Tokens.MERGE:
                    {
                        cs = compileMergeStatement(RangeVariable.emptyArray);

                        break;
                    }
                case Tokens.DELETE:
                    {
                        cs = compileDeleteStatement(RangeVariable.emptyArray);

                        break;
                    }
                case Tokens.TRUNCATE:
                    {
                        cs = compileDeleteStatement(RangeVariable.emptyArray);

                        break;
                    }

                // PROCEDURE
                case Tokens.CALL:
                    {
                        cs = compileCallStatement(
                            session.sessionContext.sessionVariablesRange, false);

                        break;
                    }

                // SQL SESSION
                case Tokens.SET:
                    cs = compileSet();
                    break;

                case Tokens.START:
                    cs = compileStartTransaction();
                    break;

                case Tokens.COMMIT:
                    cs = compileCommit();
                    break;

                case Tokens.ROLLBACK:
                    cs = compileRollback();
                    break;

                case Tokens.SAVEPOINT:
                    cs = compileSavepoint();
                    break;

                case Tokens.RELEASE:
                    cs = compileReleaseSavepoint();
                    break;

                // DDL
                case Tokens.CREATE:
                    cs = compileCreate();
                    break;

                case Tokens.ALTER:
                    cs = compileAlter();
                    break;

                case Tokens.DROP:
                    cs = compileDrop();
                    break;

                case Tokens.GRANT:
                case Tokens.REVOKE:
                    cs = compileGrantOrRevoke();
                    break;

                // HSQL SESSION
                case Tokens.LOCK:
                    cs = compileLock();
                    break;

                case Tokens.CONNECT:
                    cs = compileConnect();
                    break;

                case Tokens.DISCONNECT:
                    cs = compileDisconnect();
                    break;

                // HSQL COMMAND
                case Tokens.SCRIPT:
                    cs = compileScript();
                    break;

                case Tokens.SHUTDOWN:
                    cs = compileShutdown();
                    break;

                case Tokens.BACKUP:
                    cs = compileBackup();
                    break;

                case Tokens.CHECKPOINT:
                    cs = compileCheckpoint();
                    break;

                case Tokens.EXPLAIN:
                    cs = compileExplainPlan();
                    break;

                case Tokens.DECLARE:
                    cs = compileDeclare();
                    break;

                default:
                    throw unexpectedToken();
            }

            if (cs.type != StatementTypes.SET_SESSION_AUTHORIZATION)
            {
                cs.setSQL(getLastPart());
            }

            if (token.tokenType == Tokens.SEMICOLON)
            {
                read();
            }
            else if (token.tokenType == Tokens.X_ENDPARSE) { }

            return cs;
        }

        private Statement compileDeclare()
        {

            Statement cs;
            ColumnSchema variable = readLocalVariableDeclarationOrNull();

            if (variable != null)
            {
                Object[] args = new Object[] { variable };

                cs = new StatementSession(StatementTypes.DECLARE_VARIABLE, args);

                return cs;
            }

            cs = compileDeclareCursor();

            return cs;
        }

        private Statement compileScript()
        {

            String name = null;

            read();

            if (token.tokenType == Tokens.X_VALUE)
            {
                if (token.dataType.typeCode != Types.SQL_CHAR)
                {
                    throw Error.error(ErrorCode.X_42581);
                }

                name = token.tokenString;

                read();
            }

            Object[] args = new Object[] { name };
            Statement cs = new StatementCommand(StatementTypes.DATABASE_SCRIPT,
                                                args, null, null);
            QNameManager.QName[] names =
                database.schemaManager.getCatalogAndBaseTableNames();

            cs.readTableNames = names;

            return cs;
        }

        private Statement compileConnect()
        {

            String userName;
            String password = null;

            read();
            readThis(Tokens.USER);
            checkIsSimpleName();

            userName = token.tokenString;

            read();
            readThis(Tokens.PASSWORD);

            password = readPassword();

            Expression[] args = new Expression[] {
            new ExpressionValue(userName, SqlType.SQL_VARCHAR),
            new ExpressionValue(password, SqlType.SQL_VARCHAR)
        };
            Statement cs =
                new StatementSession(StatementTypes.SET_SESSION_AUTHORIZATION,
                                     args);

            return cs;
        }

        private StatementCommand compileSetDefault()
        {

            read();

            switch (token.tokenType)
            {

                case Tokens.INITIAL:
                    {
                        read();
                        readThis(Tokens.SCHEMA);

                        QNameManager.QName schema = database.schemaManager.getSchemaHsqlName(
                            token.tokenString);

                        read();

                        Object[] args = new Object[] { schema };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_DEFAULT_INITIAL_SCHEMA, args,
                            null, null);
                    }
                case Tokens.RESULT:
                    {
                        read();
                        readThis(Tokens.MEMORY);
                        readThis(Tokens.ROWS);

                        int size = readIntegerObject();
                        Object[] args = new Object[] { size };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_RESULT_MEMORY_ROWS, args,
                            null, null);
                    }
                case Tokens.TABLE:
                    {
                        read();
                        readThis(Tokens.TYPE);

                        int type = TableBase.MEMORY_TABLE;

                        switch (token.tokenType)
                        {

                            case Tokens.MEMORY:
                                break;

                            case Tokens.CACHED:
                                type = TableBase.CACHED_TABLE;
                                break;

                            default:
                                throw unexpectedToken();
                        }

                        read();

                        Object[] args = new Object[] { (type) };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_DEFAULT_TABLE_TYPE, args,
                            null, null);
                    }
                default:
                    throw unexpectedToken();
            }
        }

        private StatementCommand compileSetProperty()
        {

            read();

            String property;
            Object value;
            EffiProzDatabaseProperties props = database.getProperties();

            checkIsSimpleName();
            checkIsDelimitedIdentifier();

            property = token.tokenString;

            bool isbool = props.isBoolean(token.tokenString);
            bool isintegral = props.isIntegral(token.tokenString);
            bool isstring = props.isString(token.tokenString);

            if (!(isbool || isintegral || isstring))
            {
                throw Error.error(ErrorCode.X_42511);
            }

            int typeCode = isbool ? Types.SQL_BOOLEAN
                                     : isintegral ? Types.SQL_INTEGER
                                                  : Types.SQL_CHAR;

            read();

            if (token.tokenType == Tokens.TRUE)
            {
                value = true;

                if (!isbool)
                {
                    throw Error.error(ErrorCode.X_42565, token.tokenString);
                }
            }
            else if (token.tokenType == Tokens.FALSE)
            {
                value = false;

                if (!isbool)
                {
                    throw Error.error(ErrorCode.X_42565, token.tokenString);
                }
            }
            else
            {
                checkIsValue();

                value = token.tokenValue;

                if (token.dataType.typeCode != typeCode)
                {
                    throw Error.error(ErrorCode.X_42565, token.tokenString);
                }
            }

            read();

            Object[] args = new Object[] {
            property, value
        };

            return new StatementCommand(StatementTypes.SET_DATABASE_PROPERTY,
                                        args, null, null);
        }

        private Statement compileSet()
        {

            int position = base.getPosition();

            session.setScripting(false);
            read();

            switch (token.tokenType)
            {

                case Tokens.CATALOG:
                    {
                        read();

                        Expression e = XreadValueSpecificationOrNull();

                        if (e == null)
                        {
                            QNameManager.QName name = readSchemaName();
                            Object[] args = new Object[] { name };

                            return new StatementSession(StatementTypes.SET_CATALOG,
                                                        args);
                        }

                        if (!e.getDataType().isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_0P000);
                        }

                        if (e.getType() != OpTypes.VALUE
                                && (e.getType() != OpTypes.SQL_FUNCTION
                                    || !((FunctionSQL)e).isValueFunction()))
                        {
                            throw Error.error(ErrorCode.X_0P000);
                        }

                        Expression[] args1 = new Expression[] { e };

                        return new StatementSession(StatementTypes.SET_CATALOG, args1);
                    }
                case Tokens.SCHEMA:
                    {
                        read();

                        Expression e = XreadValueSpecificationOrNull();

                        if (e == null)
                        {
                            QNameManager.QName name = readSchemaName();
                            Object[] args = new Object[] { name };

                            return new StatementSession(StatementTypes.SET_SCHEMA,
                                                        args);
                        }

                        if (!e.getDataType().isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_0P000);
                        }

                        if (e.getType() != OpTypes.VALUE
                                && (e.getType() != OpTypes.SQL_FUNCTION
                                    || !((FunctionSQL)e).isValueFunction()))
                        {
                            throw Error.error(ErrorCode.X_0P000);
                        }

                        Expression[] args2 = new Expression[] { e };

                        return new StatementSession(StatementTypes.SET_SCHEMA, args2);
                    }
                case Tokens.TIME:
                    {
                        read();

                        return compileSetTimeZone();
                    }
                case Tokens.ROLE:
                    {
                        read();

                        return compileSetRole();
                    }
                case Tokens.SESSION:
                    {
                        read();

                        return compileSessionSettings();
                    }
                case Tokens.TRANSACTION:
                    {
                        read();

                        Object[] args = processTransactionCharacteristics();

                        if (args[0] == null && args[1] == null)
                        {
                            throw unexpectedToken();
                        }

                        return new StatementSession(StatementTypes.SET_TRANSACTION,
                                                    args);
                    }
                case Tokens.AUTOCOMMIT:
                    {
                        read();

                        Boolean mode = processTrueOrFalseObject();
                        Object[] args = new Object[] { mode };

                        return new StatementSession(
                            StatementTypes.SET_SESSION_AUTOCOMMIT, args);
                    }

                // deprecated
                case Tokens.READONLY:
                    {
                        read();

                        Boolean ro = processTrueOrFalseObject();
                        Object[] args = new Object[] { ro };

                        return new StatementSession(
                            StatementTypes.SET_SESSION_CHARACTERISTICS, args);
                    }
                case Tokens.IGNORECASE:
                    {
                        read();

                        Boolean mode = processTrueOrFalseObject();
                        Object[] args = new Object[] { mode };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_SQL_IGNORECASE, args, null,
                            null);
                    }
                case Tokens.MAXROWS:
                    {
                        read();

                        int size = readIntegerObject();
                        Object[] args = new Object[] { size };

                        return new StatementSession(
                            StatementTypes.SET_SESSION_RESULT_MAX_ROWS, args);
                    }

                // for backward compatibility
                case Tokens.DEFAULT:
                    {
                        read();
                        readThis(Tokens.TABLE);
                        readThis(Tokens.TYPE);

                        int type = TableBase.MEMORY_TABLE;

                        switch (token.tokenType)
                        {

                            case Tokens.MEMORY:
                                break;

                            case Tokens.CACHED:
                                type = TableBase.CACHED_TABLE;
                                break;

                            default:
                                throw unexpectedToken();
                        }

                        read();

                        Object[] args = new Object[] { type };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_DEFAULT_TABLE_TYPE, args,
                            null, null);
                    }
                case Tokens.RESULT:
                    {
                        read();
                        readThis(Tokens.MEMORY);
                        readThis(Tokens.SIZE);

                        int size = readIntegerObject();
                        Object[] args = new Object[] { size };

                        return new StatementSession(
                            StatementTypes.SET_SESSION_RESULT_MEMORY_ROWS, args);
                    }
                case Tokens.TABLE:
                    {
                        read();

                        Table t = readTableName();
                        Object[] args = new Object[] {
                    t.tableName, null
                };

                        switch (token.tokenType)
                        {

                            default:
                                {
                                    throw unexpectedToken();
                                }
                            case Tokens.SOURCE:
                                read();

                                return compileTextTableSource(t);

                            case Tokens.READ:
                                {
                                    read();

                                    bool rd = false;

                                    if (token.tokenType == Tokens.WRITE) { }
                                    else
                                    {
                                        readThis(Tokens.ONLY);

                                        rd = true;
                                    }

                                    args[1] = (rd);

                                    return new StatementCommand(
                                        StatementTypes.SET_TABLE_READONLY, args, null,
                                        t.getName());
                                }

                            // deprecated
                            case Tokens.READONLY:
                                {
                                    read();

                                    Boolean rd = processTrueOrFalseObject();

                                    args[1] = rd;

                                    return new StatementCommand(
                                        StatementTypes.SET_TABLE_READONLY, args, null,
                                        t.getName());
                                }
                            case Tokens.INDEX:
                                {
                                    String value;

                                    read();
                                    checkIsValue();

                                    value = token.tokenString;

                                    read();

                                    args[1] = value;

                                    return new StatementCommand(
                                        StatementTypes.SET_TABLE_INDEX, args, null, null);
                                }
                            case Tokens.TYPE:
                                {
                                    read();

                                    int newType;

                                    if (token.tokenType == Tokens.CACHED)
                                    {
                                        newType = TableBase.CACHED_TABLE;
                                    }
                                    else if (token.tokenType == Tokens.MEMORY)
                                    {
                                        newType = TableBase.MEMORY_TABLE;
                                    }
                                    else
                                    {
                                        throw Error.error(ErrorCode.X_42581);
                                    }

                                    read();

                                    args[1] = newType;

                                    return new StatementCommand(
                                        StatementTypes.SET_TABLE_TYPE, args, null,
                                        t.getName());
                                }
                        }
                    }
                /*
                            case Tokens.CHECKPOINT : {
                                read();
                                readThis(Tokens.DEFRAG);

                                int      size = readInteger();
                                Object[] args = new Object[]{ new Integer(size) };

                                return new StatementCommand(
                                    StatementTypes.SET_DATABASE_FILES_DEFRAG, args, null,
                                    null);
                            }
                */
                case Tokens.WRITE_DELAY:
                    {
                        read();

                        int delay = 0;

                        if (token.tokenType == Tokens.TRUE)
                        {
                            delay = database.getProperties().getDefaultWriteDelay();

                            read();
                        }
                        else if (token.tokenType == Tokens.FALSE)
                        {
                            delay = 0;

                            read();
                        }
                        else
                        {
                            delay = this.readInteger();

                            if (delay < 0)
                            {
                                delay = 0;
                            }

                            if (token.tokenType == Tokens.MILLIS)
                            {
                                read();
                            }
                            else
                            {
                                delay *= 1000;
                            }
                        }

                        Object[] args = new Object[] { (delay) };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_FILES_WRITE_DELAY, args, null,
                            null);
                    }
                case Tokens.PASSWORD:
                    {
                        String password;

                        read();

                        password = readPassword();

                        Object[] args = new Object[] {
                    null, password
                };

                        return new StatementCommand(StatementTypes.SET_USER_PASSWORD,
                                                    args, null, null);
                    }
                case Tokens.INITIAL:
                    {
                        read();
                        readThis(Tokens.SCHEMA);

                        QNameManager.QName schema;

                        if (token.tokenType == Tokens.DEFAULT)
                        {
                            schema = null;
                        }
                        else
                        {
                            schema = database.schemaManager.getSchemaHsqlName(
                                token.tokenString);
                        }

                        read();

                        Object[] args = new Object[] {
                    null, schema
                };

                        return new StatementCommand(
                            StatementTypes.SET_USER_INITIAL_SCHEMA, args, null, null);
                    }
                case Tokens.FILES:
                    {
                        return compileSetFilesProperty();
                    }
                case Tokens.DATABASE:
                    {
                        return compileSetDatabaseProperty();
                    }
                case Tokens.PROPERTY:
                    {
                        return compileSetProperty();
                    }
                default:
                    {
                        rewind(position);

                        return compileSetStatement(
                            session.sessionContext.sessionVariablesRange);
                    }
            }
        }

        public StatementCommand compileSetDatabaseProperty()
        {

            read();

            String name;

            switch (token.tokenType)
            {

                case Tokens.COLLATION:
                    {
                        read();
                        checkIsSimpleName();

                        name = token.tokenString;

                        read();

                        Object[] args = new Object[] { name };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_SQL_COLLATION, args, null,
                            null);
                    }
                case Tokens.DEFAULT:
                    {
                        return compileSetDefault();
                    }
                case Tokens.EVENT:
                    {
                        read();
                        readThis(Tokens.LOG);
                        readThis(Tokens.LEVEL);

                        int value = readIntegerObject();
                        Object[] args = new Object[] { value };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_FILES_EVENT_LOG, args, null,
                            null);
                    }
                case Tokens.GC:
                    {
                        read();

                        int size = readIntegerObject();
                        Object[] args = new Object[] { size };

                        return new StatementCommand(StatementTypes.SET_DATABASE_GC,
                                                    args, null, null);
                    }
                case Tokens.REFERENTIAL:
                    {
                        read();
                        readThis(Tokens.INTEGRITY);

                        bool mode = processTrueOrFalse();
                        Object[] args = new Object[] { (mode) };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_SQL_REFERENTIAL_INTEGRITY,
                            args, null, null);
                    }
                case Tokens.SQL:
                    {
                        read();

                        int type = 0;
                        Boolean? flag = null;

                        switch (token.tokenType)
                        {

                            case Tokens.SIZE:
                                read();

                                type = StatementTypes.SET_DATABASE_SQL_STRICT_SIZE;
                                flag = processTrueOrFalseObject();
                                break;

                            case Tokens.NAMES:
                                read();

                                type = StatementTypes.SET_DATABASE_SQL_STRICT_NAMES;
                                flag = processTrueOrFalseObject();
                                break;

                            default:
                                unexpectedToken();
                                break;
                        }

                        Object[] args = new Object[] { flag };

                        return new StatementCommand(type, args, null, null);
                    }
                case Tokens.TEXT:
                    {
                        read();
                        readThis(Tokens.TABLE);
                        readThis(Tokens.DEFAULTS);

                        String source = readQuotedString();
                        Object[] args = new Object[] { source };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_TEXT_SOURCE, args, null, null);
                    }
                case Tokens.TRANSACTION:
                    {
                        read();
                        readThis(Tokens.CONTROL);

                        int mode = Database.LOCKS;

                        switch (token.tokenType)
                        {

                            case Tokens.MVCC:
                                read();

                                mode = Database.MVCC;
                                break;

                            case Tokens.MVLOCKS:
                                read();

                                mode = Database.MVLOCKS;
                                break;

                            case Tokens.LOCKS:
                                read();

                                mode = Database.LOCKS;
                                break;
                        }

                        Object[] args = new Object[] { (mode) };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_TRANSACTION_CONTROL, args,
                            null, null);
                    }
                case Tokens.UNIQUE:
                    {
                        read();
                        readThis(Tokens.NAME);
                        isUndelimitedSimpleName();

                        name = token.tokenString;

                        read();

                        /** @todo - only digits, letters and underscore */
                        if (name.Length != 18)
                        {
                            throw Error.error(ErrorCode.X_42511);
                        }

                        if (!Charset.isInSet(name, Charset.unquotedIdentifier)
                                || !Charset.startsWith(name,
                                                       Charset.uppercaseLetters))
                        {
                            throw Error.error(ErrorCode.X_42501);
                        }

                        Object[] args = new Object[] { name };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_UNIQUE_NAME, args, null, null);
                    }
                default:
                    {
                        throw unexpectedToken();
                    }
            }
        }

        public StatementCommand compileSetFilesProperty()
        {

            read();

            //String name;
            int type = 0;
            Boolean? flag = null;
            int? value = null;

            switch (token.tokenType)
            {

                case Tokens.LOCK:
                    {
                        read();

                        flag = processTrueOrFalseObject();
                        type = StatementTypes.SET_DATABASE_FILES_LOCK;

                        break;
                    }
                case Tokens.CACHE:
                    {
                        read();

                        if (readIfThis(Tokens.SIZE))
                        {
                            value = readIntegerObject();
                            type = StatementTypes.SET_DATABASE_FILES_CACHE_SIZE;
                        }
                        else if (readIfThis(Tokens.ROWS))
                        {
                            value = readIntegerObject();
                            type = StatementTypes.SET_DATABASE_FILES_CACHE_ROWS;
                        }

                        break;
                    }
                case Tokens.SCALE:
                    {
                        read();

                        value = readIntegerObject();
                        type = StatementTypes.SET_DATABASE_FILES_SCALE;

                        break;
                    }
                case Tokens.DEFRAG:
                    {
                        read();

                        type = StatementTypes.SET_DATABASE_FILES_DEFRAG;
                        value = readIntegerObject();

                        break;
                    }
                case Tokens.NIO:
                    {
                        read();

                        type = StatementTypes.SET_DATABASE_FILES_NIO;
                        flag = processTrueOrFalseObject();

                        break;
                    }
                case Tokens.BACKUP:
                    {
                        read();

                        type = StatementTypes.SET_DATABASE_FILES_BACKUP_INCREMENT;

                        readThis(Tokens.INCREMENT);

                        flag = processTrueOrFalseObject();

                        break;
                    }
                case Tokens.LOG:
                    {
                        read();
                        readThis(Tokens.SIZE);
                        checkDatabaseUpdateAuthorisation();

                        type = StatementTypes.SET_DATABASE_FILES_LOG_SIZE;
                        value = readIntegerObject();

                        break;
                    }
                case Tokens.TEMP:
                    {
                        read();
                        readThis(Tokens.PATH);
                        checkDatabaseUpdateAuthorisation();

                        type = StatementTypes.SET_DATABASE_FILES_TEMP_PATH;
                        value = readIntegerObject();

                        break;
                    }
                case Tokens.WRITE:
                    {
                        read();
                        readThis(Tokens.DELAY);

                        type = StatementTypes.SET_DATABASE_FILES_WRITE_DELAY;

                        int delay = 0;

                        if (token.tokenType == Tokens.TRUE)
                        {
                            delay = database.getProperties().getDefaultWriteDelay();

                            read();
                        }
                        else if (token.tokenType == Tokens.FALSE)
                        {
                            delay = 0;

                            read();
                        }
                        else
                        {
                            delay = this.readInteger();

                            if (delay < 0)
                            {
                                delay = 0;
                            }

                            if (token.tokenType == Tokens.MILLIS)
                            {
                                read();
                            }
                            else
                            {
                                delay *= 1000;
                            }
                        }

                        value = (delay);

                        break;
                    }
                default:
                    throw unexpectedToken();
            }

            Object[] args = new Object[]{ flag == null ? (object)value
                                                   : flag };

            return new StatementCommand(type, args, database.getCatalogName(),
                                        null);
        }

        Object[] processTransactionCharacteristics()
        {

            int level = 0;
            bool rdy = false;
            Object[] args = new Object[2];

       
            while (true)
            {
                switch (token.tokenType)
                {

                    case Tokens.READ:
                        {
                            if (args[0] != null)
                            {
                                throw unexpectedToken();
                            }

                            read();

                            if (token.tokenType == Tokens.ONLY)
                            {
                                read();

                                rdy = true;
                            }
                            else
                            {
                                readThis(Tokens.WRITE);

                                rdy = false;
                            }

                            args[0] = rdy;

                            break;
                        }
                    case Tokens.ISOLATION:
                        {
                            if (args[1] != null)
                            {
                                throw unexpectedToken();
                            }

                            read();
                            readThis(Tokens.LEVEL);

                            switch (token.tokenType)
                            {

                                case Tokens.SERIALIZABLE:
                                    read();

                                    level = SessionTxConsts.TX_SERIALIZABLE;
                                    break;

                                case Tokens.READ:
                                    read();

                                    if (token.tokenType == Tokens.COMMITTED)
                                    {
                                        read();

                                        level = SessionTxConsts.TX_READ_COMMITTED;
                                    }
                                    else if (token.tokenType == Tokens.UNCOMMITTED)
                                    {
                                        read();

                                        level = SessionTxConsts.TX_READ_UNCOMMITTED;
                                    }
                                    else
                                    {
                                        throw unexpectedToken();
                                    }
                                    break;

                                case Tokens.REPEATABLE:
                                    read();
                                    readThis(Tokens.READ);

                                    level = SessionTxConsts.TX_REPEATABLE_READ;
                                    break;

                                default:
                                    throw unexpectedToken();
                            }

                            args[1] = (level);

                            break;
                        }
                    default:
                        {
                            goto outerloop;
                        }
                }
            }
        outerloop:
            if (!rdy && level == 1)
            {
                throw unexpectedToken(Tokens.T_WRITE);
            }

            return args;
        }

        /**
         * Retrieves bool value corresponding to the next token.
         *
         * @return   true if next token is "TRUE"; false if next token is "FALSE"
         * @throws  CoreException if the next token is neither "TRUE" or "FALSE"
         */
        private bool processTrueOrFalse()
        {

            if (token.namePrefix != null)
            {
                throw unexpectedToken();
            }

            if (token.tokenType == Tokens.TRUE)
            {
                read();

                return true;
            }
            else if (token.tokenType == Tokens.FALSE)
            {
                read();

                return false;
            }
            else
            {
                throw unexpectedToken();
            }
        }

        private Boolean processTrueOrFalseObject()
        {

            if (token.namePrefix != null)
            {
                throw unexpectedToken();
            }

            if (token.tokenType == Tokens.TRUE)
            {
                read();

                return true;
            }
            else if (token.tokenType == Tokens.FALSE)
            {
                read();

                return false;
            }
            else
            {
                throw unexpectedToken();
            }
        }

        /**
         * Responsible for  handling the execution of COMMIT [WORK]
         *
         * @throws  CoreException
         */
        private Statement compileCommit()
        {

            bool chain = false;

            read();
            readIfThis(Tokens.WORK);

            if (token.tokenType == Tokens.AND)
            {
                read();

                if (token.tokenType == Tokens.NO)
                {
                    read();
                }
                else
                {
                    chain = true;
                }

                readThis(Tokens.CHAIN);
            }

            String sql = getLastPart();
            Object[] args = new Object[] { (chain) };
            Statement cs = new StatementSession(StatementTypes.COMMIT_WORK, args);

            return cs;
        }

        private Statement compileStartTransaction()
        {

            read();
            readThis(Tokens.TRANSACTION);

            Object[] args = processTransactionCharacteristics();
            Statement cs = new StatementSession(StatementTypes.START_TRANSACTION,
                                                args);

            return cs;
        }

        private Statement compileLock()
        {

            read();
            readThis(Tokens.TABLE);

            OrderedHashSet readSet = new OrderedHashSet();
            OrderedHashSet writeSet = new OrderedHashSet();


            while (true)
            {
                Table table = readTableName();

                switch (token.tokenType)
                {

                    case Tokens.READ:
                        read();
                        readSet.add(table.getName());
                        break;

                    case Tokens.WRITE:
                        read();
                        writeSet.add(table.getName());
                        break;

                    default:
                        throw unexpectedToken();
                }

                if (token.tokenType == Tokens.COMMA)
                {
                    read();

                    continue;
                }

                break;
            }

            QNameManager.QName[] readTableNames = new QNameManager.QName[readSet.size()];

            readSet.toArray(readTableNames);

            QNameManager.QName[] writeTableNames = new QNameManager.QName[writeSet.size()];

            writeSet.toArray(writeTableNames);

            Statement cs =
                new StatementSession(StatementTypes.TRANSACTION_LOCK_TABLE,
                                     readTableNames, writeTableNames);

            return cs;
        }

        private Statement compileRollback()
        {

            bool chain = false;
            String savepoint = null;

            read();

            if (token.tokenType == Tokens.TO)
            {
                read();
                readThis(Tokens.SAVEPOINT);
                checkIsSimpleName();

                savepoint = token.tokenString;

                read();

                String sql = getLastPart();
                Object[] args = new Object[] { savepoint };
                Statement cs =
                    new StatementSession(StatementTypes.ROLLBACK_SAVEPOINT, args);

                return cs;
            }
            else
            {
                if (token.tokenType == Tokens.WORK)
                {
                    read();
                }

                if (token.tokenType == Tokens.AND)
                {
                    read();

                    if (token.tokenType == Tokens.NO)
                    {
                        read();
                    }
                    else
                    {
                        chain = true;
                    }

                    readThis(Tokens.CHAIN);
                }
            }

            String sql1 = getLastPart();
            Object[] args1 = new Object[] { (chain) };
            Statement cs1 = new StatementSession(StatementTypes.ROLLBACK_WORK,
                                                args1);

            return cs1;
        }

        private Statement compileSavepoint()
        {

            String name;

            read();
            checkIsSimpleName();

            name = token.tokenString;

            read();

            String sql = getLastPart();
            Object[] args = new Object[] { name };

            return new StatementSession(StatementTypes.SAVEPOINT, args);
        }

        private Statement compileReleaseSavepoint()
        {

            read();
            readThis(Tokens.SAVEPOINT);

            String name = token.tokenString;

            read();

            String sql = getLastPart();
            Object[] args = new Object[] { name };

            return new StatementSession(StatementTypes.RELEASE_SAVEPOINT, args);
        }

        private Statement compileSessionSettings()
        {

            if (token.tokenType == Tokens.CHARACTERISTICS)
            {
                read();
                readThis(Tokens.AS);

                Object[] args = processTransactionCharacteristics();

                return new StatementSession(
                    StatementTypes.SET_SESSION_CHARACTERISTICS, args);
            }
            else if (token.tokenType == Tokens.AUTHORIZATION)
            {
                read();

                Expression e = XreadValueSpecificationOrNull();

                if (e == null)
                {
                    throw Error.error(ErrorCode.X_42584);
                }

                e.resolveTypes(session, null);

                if (e.isParam())
                {
                    e.dataType = SqlType.SQL_VARCHAR;
                }

                if (e.dataType == null || !e.dataType.isCharacterType())
                {
                    throw Error.error(ErrorCode.X_42565);
                }

                Expression[] args = new Expression[] {
                e, null
            };

                return new StatementSession(
                    StatementTypes.SET_SESSION_AUTHORIZATION, args);
            }

            throw unexpectedToken();
        }

        private Statement compileSetRole()
        {

            Expression e;

            if (token.tokenType == Tokens.NONE)
            {
                read();

                e = new ExpressionValue(null, SqlType.SQL_VARCHAR);
            }
            else
            {
                e = XreadValueSpecificationOrNull();

                if (e == null)
                {
                    throw Error.error(ErrorCode.X_2A000);
                }

                if (!e.getDataType().isCharacterType())
                {
                    throw Error.error(ErrorCode.X_0P000);
                }

                if (e.getType() != OpTypes.VALUE
                        && (e.getType() != OpTypes.SQL_FUNCTION
                            || !((FunctionSQL)e).isValueFunction()))
                {
                    throw Error.error(ErrorCode.X_0P000);
                }
            }

            String sql = getLastPart();

            return new StatementSession(StatementTypes.SET_ROLE,
                                        new Expression[] { e });
        }

        private Statement compileSetTimeZone()
        {

            Expression e;

            readThis(Tokens.ZONE);

            if (token.tokenType == Tokens.LOCAL)
            {
                read();

                e = new ExpressionValue(null, SqlType.SQL_INTERVAL_HOUR_TO_MINUTE);
            }
            else
            {
                e = XreadIntervalValueExpression();

                EfzList unresolved =
                    e.resolveColumnReferences(RangeVariable.emptyArray, null);

                ExpressionColumn.checkColumnsResolved(unresolved);
                e.resolveTypes(session, null);

                if (e.dataType == null)
                {
                    throw Error.error(ErrorCode.X_42565);
                }

                if (e.dataType.typeCode != Types.SQL_INTERVAL_HOUR_TO_MINUTE)
                {
                    throw Error.error(ErrorCode.X_42565);
                }
            }

            String sql = getLastPart();

            return new StatementSession(StatementTypes.SET_TIME_ZONE,
                                        new Expression[] { e });
        }

        private Statement compileShutdown()
        {

            int closemode;

            session.checkAdmin();

            closemode = Database.CLOSEMODE_NORMAL;

            read();

            switch (token.tokenType)
            {

                case Tokens.IMMEDIATELY:
                    closemode = Database.CLOSEMODE_IMMEDIATELY;

                    read();
                    break;

                case Tokens.COMPACT:
                    closemode = Database.CLOSEMODE_COMPACT;

                    read();
                    break;

                case Tokens.SCRIPT:
                    closemode = Database.CLOSEMODE_SCRIPT;

                    read();
                    break;

                // only semicolon is accepted here
            }

            if (token.tokenType == Tokens.SEMICOLON)
            {
                read();
            }

            if (token.tokenType != Tokens.X_ENDPARSE)
            {
                throw unexpectedToken();
            }

            String sql = getLastPart();
            Object[] args = new Object[] { (closemode) };
            Statement cs = new StatementCommand(StatementTypes.DATABASE_SHUTDOWN,
                                                args, null, null);

            return cs;
        }

        private Statement compileBackup()
        {

            read();
            readThis(Tokens.DATABASE);
            readThis(Tokens.TO);
            checkIsValue();

            String path = readQuotedString();
            Boolean? blockingMode = null;    // Default to non-blocking
            Boolean? scriptMode = null;    // Default to non-script
            Boolean? compression = null;    // Defaults to compressed


            while (true)
            {
                switch (token.tokenType)
                {

                    case Tokens.BLOCKING:
                        if (blockingMode != null)
                        {
                            throw unexpectedToken();
                        }

                        blockingMode = true;

                        read();
                        break;

                    case Tokens.SCRIPT:
                        if (scriptMode != null)
                        {
                            throw unexpectedToken();
                        }

                        scriptMode = true;

                        read();
                        break;

                    case Tokens.COMPRESSED:
                        if (compression != null)
                        {
                            throw unexpectedToken();
                        }

                        compression = true;

                        read();
                        break;

                    case Tokens.NOT:
                        read();

                        if (token.tokenType == Tokens.COMPRESSED)
                        {
                            if (compression != null)
                            {
                                throw unexpectedToken();
                            }

                            compression = false;

                            read();
                        }
                        else if (token.tokenType == Tokens.BLOCKING)
                        {
                            blockingMode = false;

                            read();
                        }
                        else
                        {
                            throw unexpectedToken();
                        }
                        break;

                    default:
                        goto outerLoop;
                }
            }

            /**
             * @todo: This block is TEMPORARY.  Will be removed when we implement
             * Non-Blocking and SCRIPT mode.
             */
        outerLoop:
            if (scriptMode != null)
            {
                throw unsupportedFeature("SCRIPT");
            }

            scriptMode = false;

            if (blockingMode == null)
            {
                throw unexpectedTokenRequire("BLOCKING");
            }

            if (compression == null)
            {
                compression = true;
            }

            Statement cs = new StatementCommand(StatementTypes.DATABASE_BACKUP,
                                                new Object[] {
            path, blockingMode, scriptMode, compression,
        }, null, null);
            QNameManager.QName[] names =
                database.schemaManager.getCatalogAndBaseTableNames();

            cs.writeTableNames = names;

            return cs;
        }

        private Statement compileCheckpoint()
        {

            bool defrag = false;

            read();

            if (token.tokenType == Tokens.DEFRAG)
            {
                defrag = true;

                read();
            }
            else if (token.tokenType == Tokens.SEMICOLON)
            {
                read();

                // only semicolon is accepted here
            }

            if (token.tokenType != Tokens.X_ENDPARSE)
            {
                throw unexpectedToken();
            }

            String sql = getLastPart();

            return getCheckpointStatement(database, defrag);
        }

        public static Statement getCheckpointStatement(Database database,
                bool defrag)
        {

            Object[] args = new Object[] { (defrag) };
            Statement cs = new StatementCommand(StatementTypes.DATABASE_CHECKPOINT,
                                                args, null, null);
            QNameManager.QName[] names =
                database.schemaManager.getCatalogAndBaseTableNames();

            cs.writeTableNames = names;

            return cs;
        }

        private Statement compileDisconnect()
        {

            read();

            //String sql = Tokens.T_DISCONNECT;
            Statement cs = new StatementSession(StatementTypes.DISCONNECT,
                                                (Object[])null);

            return cs;
        }

        private Statement compileExplainPlan()
        {

            Statement cs;

            read();
            readThis(Tokens.PLAN);
            readThis(Tokens.FOR);

            cs = compilePart();
            cs.writeTableNames = QNameManager.QName.emptyArray;
            cs.readTableNames = QNameManager.QName.emptyArray;

            cs.setDescribe();

            return cs;
        }

        private Statement compileTextTableSource(Table t)
        {

            bool isSourceHeader = false;
            bool isDesc = false;
            String source;
            Object[] args = new Object[5];

            args[0] = t.tableName;

            if (!t.isText())
            {
                Exception e = Error.error(ErrorCode.X_S0522);
            }

            // SET TABLE <table> SOURCE ON
            if (token.tokenType == Tokens.ON)
            {
                read();

                String sql = getLastPart();

                args[1] = true;

                return new StatementCommand(StatementTypes.SET_TABLE_SOURCE, args,
                                            null, t.getName());
            }
            else if (token.tokenType == Tokens.OFF)
            {
                read();

                String sql = getLastPart();

                args[1] = false;

                return new StatementCommand(StatementTypes.SET_TABLE_SOURCE, args,
                                            null, t.getName());
            }
            else if (token.tokenType == Tokens.HEADER)
            {
                read();

                isSourceHeader = true;
            }

            if (token.tokenType != Tokens.X_DELIMITED_IDENTIFIER
                    && (token.tokenType != Tokens.X_VALUE
                        || !token.dataType.isCharacterType()))
            {
                throw Error.error(ErrorCode.X_42581);
            }

            source = token.tokenString;

            read();

            if (!isSourceHeader && token.tokenType == Tokens.DESC)
            {
                isDesc = true;

                read();
            }

            String sql1 = getLastPart();

            args[2] = source;
            args[3] = (isDesc);
            args[4] = (isSourceHeader);

            int type = isSourceHeader ? StatementTypes.SET_TABLE_SOURCE_HEADER
                                      : StatementTypes.SET_TABLE_SOURCE;

            return new StatementCommand(type, args, null, t.getName());
        }
    }
}
